package com.xdhl.fu.fim.xxljob.job;

import com.xdhl.fu.common.core.constant.flightinfo.FlightInfoConstants;
import com.xdhl.fu.common.core.task.IJob;
import com.xdhl.fu.common.core.utils.DateUtils;
import com.xdhl.fu.common.core.utils.SpringUtils;
import com.xdhl.fu.common.redis.service.RedisService;
import com.xdhl.fu.common.security.utils.SecurityUtils;
import com.xdhl.fu.fim.price.strategy.PriceCalcStrategy;
import com.xdhl.fu.fim.service.IFlightInfoService;
import com.xdhl.fu.fim.service.IFlightScheduleService;
import com.xdhl.fu.fim.service.IFlightStockService;
import com.xdhl.fu.flight.info.api.domain.FlightInfo;
import com.xdhl.fu.flight.info.api.domain.FlightSchedule;
import com.xdhl.fu.flight.info.api.domain.FlightStock;
import com.xdhl.fu.flight.info.api.mongodb.MongodbConstants;
import com.xdhl.fu.flight.info.api.mongodb.domain.MGFlightSchedule;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 航班排班表初始化任务
 *
 * @author 朱力维
 * @time 2022/6/18 14:19
 */
@Component
public class FlightScheduleInitJob implements IJob {

    private static final Logger log = LoggerFactory.getLogger(FlightScheduleInitJob.class);

    /**
     * 天数偏移量
     */
    public static final int DAY_OFFSET = 14;

    /**
     * 飞机时速
     */
    public static final double speed = 828D;

    /**
     * 是否含有mongodb文档
     */
    private boolean hasCollection = false;

    /**
     * 当前偏移量
     */
    private Integer currentOffset;

    /**
     * 缓存有效期
     */
    public static final int CACHE_DUE = DAY_OFFSET + 1;

    @Value("${spring.profiles.active}")
    private String profile;

    @Resource
    private IFlightInfoService flightInfoService;

    @Resource
    private IFlightScheduleService flightScheduleService;

    @Resource
    private IFlightStockService flightStockService;

    @Resource
    private RedisService redisService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private MongoTemplate mongoTemplate;

    @XxlJob(value = "flightScheduleInitJob", init = "init", destroy = "destroy")
    public ReturnT<String> execute(String param) {
        try {
            for (int i = 1; i <= DAY_OFFSET; i++) {
                this.currentOffset = i;
                this.start();
            }
            log.info("航班排班表初始化成功。");
            return ReturnT.SUCCESS;
        } catch (Exception e) {
            log.error("航班排班表初始化失败。" + e);
            return ReturnT.FAIL;
        }
    }

    @Override
    public void start() throws Exception {
        List<FlightInfo> flightInfos = flightInfoService.list();

        flightInfos.forEach(flightInfo -> {
            // 添加航班计划
            FlightSchedule flightSchedule = addSchedule(flightInfo);

            // 添加库存信息
            FlightStock flightStock = addStock(flightSchedule);

            // 添加至Mongodb
            addMongodb(flightInfo, flightSchedule, flightStock);
        });
    }

    @Override
    public void init() {

    }

    @Override
    public void destroy() {

    }

    /**
     * 添加航班计划
     *
     * @param flightInfo 航班信息
     */
    private FlightSchedule addSchedule(FlightInfo flightInfo) {
        LocalDate startDay = LocalDate.now().plusDays(currentOffset);
        LocalDateTime startTime = LocalDateTime.of(startDay, LocalTime.of(flightInfo.getHour(), flightInfo.getMinute(), 0));
        FlightSchedule flightSchedule = new FlightSchedule();

        // 航班信息
        flightSchedule.setFlightDataId(flightInfo.getDataId());
        flightSchedule.setAircraftModel(flightInfo.getAircraftModel());

        // 计算时长
        flightSchedule.setDuration(Double.valueOf(flightInfo.getDistance() / speed * 60D).intValue());

        // 时间信息
        flightSchedule.setStartDate(startDay);
        flightSchedule.setStartTime(startTime);
        flightSchedule.setEndDate(startDay);
        flightSchedule.setEndTime(startTime.plusMinutes(flightSchedule.getDuration()));

        // 价格信息
        calcPrice(flightInfo, flightSchedule);

        // 设置基本信息
        flightSchedule.setCreateBy(SecurityUtils.getUsername());
        flightSchedule.setCreateTime(LocalDateTime.now());
        flightSchedule.setUpdateBy(SecurityUtils.getUsername());
        flightSchedule.setUpdateTime(flightSchedule.getCreateTime());

        flightScheduleService.save(flightSchedule);
        return flightSchedule;
    }

    private FlightStock addStock(FlightSchedule flightSchedule) {
        FlightStock flightStock = new FlightStock();
        // 航班信息
        flightStock.setFlightScheduleDataId(flightSchedule.getDataId());

        // 总库存 TODO 待优化
        flightStock.setEconomySeatCount(138);
        flightStock.setBusinessSeatCount(12);
        flightStock.setHeadSeatCount(8);

        // 剩余库存
        flightStock.setEconomySeatRemainderCount(flightStock.getEconomySeatCount());
        flightStock.setBusinessSeatRemainderCount(flightStock.getBusinessSeatCount());
        flightStock.setHeadSeatRemainderCount(flightStock.getHeadSeatCount());

        // 设置基本信息
        flightStock.setCreateBy(SecurityUtils.getUsername());
        flightStock.setCreateTime(LocalDateTime.now());
        flightStock.setUpdateBy(SecurityUtils.getUsername());
        flightStock.setUpdateTime(flightSchedule.getCreateTime());

        flightStockService.save(flightStock);

        // 灰度和正式环境才预先将库存信息缓存至redis
        if ("prod".equals(profile) || "gray".equals(profile)) {
            this.cache(flightStock);
        }

        return flightStock;
    }

    /**
     * 添加打包信息至mongodb
     *
     * @param flightInfo     航线信息
     * @param flightSchedule 航班计划信息
     * @param flightStock    航班库存信息
     */
    private void addMongodb(FlightInfo flightInfo, FlightSchedule flightSchedule, FlightStock flightStock) {
        if (hasCollection()) {
            MGFlightSchedule mgFlightSchedule = new MGFlightSchedule();
            mgFlightSchedule.setDataId(flightSchedule.getDataId());
            mgFlightSchedule.setFlightSchedule(flightSchedule);
            mgFlightSchedule.setFlightStock(flightStock);
            mgFlightSchedule.setFlightInfo(flightInfo);
            mgFlightSchedule.setFlightNumber(flightInfo.getFlightNumber());
            mgFlightSchedule.setStartDate(flightSchedule.getStartDate().format(DateTimeFormatter.ofPattern(DateUtils.YYYY_MM_DD)));
            mgFlightSchedule.setStartStation(flightInfo.getStartStation());
            mgFlightSchedule.setEndStation(flightInfo.getEndStation());
            mgFlightSchedule.setFGroup(flightInfo.getFGroup());
            mgFlightSchedule.setFGroupOrder(flightInfo.getFGroupOrder());
            mgFlightSchedule.setFlightType(flightInfo.getFlightType());

            // 设置城市名称
            BoundHashOperations<String, String, Object> boundHashOperations = stringRedisTemplate.boundHashOps(FlightInfoConstants.AIRPORTS_CACHE_KEY);
            setAirportName(mgFlightSchedule, boundHashOperations);
            mongoTemplate.save(mgFlightSchedule);
        }
    }

    /**
     * 是否含有mongodb文档
     *
     * @return 是否含有mongodb文档
     */
    private boolean hasCollection() {
        if (hasCollection) {
            return true;
        }
        if (!mongoTemplate.collectionExists(MongodbConstants.COLLECTION_NAME_FLIGHT_SCHEDULE)) {
            mongoTemplate.createCollection(MongodbConstants.COLLECTION_NAME_FLIGHT_SCHEDULE);
        }
        hasCollection = true;
        return true;
    }

    /**
     * 调用计算策略链计算价格
     *
     * @param flightInfo 航班信息
     */
    private void calcPrice(FlightInfo flightInfo, FlightSchedule schedule) {
        Map<String, PriceCalcStrategy> strategies = SpringUtils.getBeans(PriceCalcStrategy.class);
        strategies.values().stream().sorted().forEach(strategy -> strategy.calc(flightInfo, schedule));
    }

    /**
     * 设置库存缓存
     *
     * @param flightStock 库存信息
     */
    private void cache(FlightStock flightStock) {
        // 存入缓存数据库
        String keyA = getKeyPrefix(flightStock) + "A";
        String keyB = getKeyPrefix(flightStock) + "B";
        String keyC = getKeyPrefix(flightStock) + "C";
        redisService.incr(keyA, flightStock.getHeadSeatRemainderCount());
        redisService.incr(keyB, flightStock.getBusinessSeatRemainderCount());
        redisService.incr(keyC, flightStock.getEconomySeatRemainderCount());

        // 设置缓存过期时间
        redisService.expire(keyA, CACHE_DUE, TimeUnit.DAYS);
        redisService.expire(keyB, CACHE_DUE, TimeUnit.DAYS);
        redisService.expire(keyC, CACHE_DUE, TimeUnit.DAYS);
    }

    private String getKeyPrefix(FlightStock flightStock) {
        return String.format("%s:%s:", FlightStock.class.getSimpleName(), flightStock.getFlightScheduleDataId());
    }

    /**
     * 设置机场中文名
     *
     * @param flightSchedule      航班排班信息
     * @param boundHashOperations 缓存绑定对象
     */
    private void setAirportName(MGFlightSchedule flightSchedule, BoundHashOperations<String, String, Object> boundHashOperations) {
        flightSchedule.setStartStationDisplay(boundHashOperations.get(flightSchedule.getFlightInfo().getStartStation()).toString());
        flightSchedule.setEndStationDisplay(boundHashOperations.get(flightSchedule.getFlightInfo().getEndStation()).toString());
    }

}
